package org.infodavid.common.impl.services;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.FileNameMap;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;

import javax.persistence.PersistenceException;
import javax.swing.text.Document;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import sun.net.www.MimeTable;

import org.infodavid.common.annotations.AImplementation;
import org.infodavid.common.annotations.AImplementation.ERuntime;
import org.infodavid.common.dto.ITemplateCategoryListItem;
import org.infodavid.common.dto.ITemplateListItem;
import org.infodavid.common.dto.ITemplateVersionListItem;
import org.infodavid.common.impl.services.helpers.ServiceHelper;
import org.infodavid.common.io.IOUtil;
import org.infodavid.common.model.ICategory;
import org.infodavid.common.model.ITemplate;
import org.infodavid.common.model.ITemplateCategory;
import org.infodavid.common.model.ITemplateVersion;
import org.infodavid.common.model.IUser;
import org.infodavid.common.model.components.IOwnerDetails.EVisibility;
import org.infodavid.common.model.criteria.ICategoryCriteria;
import org.infodavid.common.model.criteria.ITemplateCriteria;
import org.infodavid.common.model.criteria.ITemplateVersionCriteria;
import org.infodavid.common.persistence.TemplateCategoryDataService;
import org.infodavid.common.persistence.TemplateDataService;
import org.infodavid.common.persistence.TemplateVersionDataService;
import org.infodavid.common.persistence.UserDataService;
import org.infodavid.common.services.ApplicationSettingsService;
import org.infodavid.common.services.IApplicationContext;
import org.infodavid.common.services.TemplateService;
import org.infodavid.common.services.exception.IllegalOperationException;
import org.infodavid.common.services.exceptions.ServiceException;

/**
 * The Class TemplateServiceImpl.
 */
@SuppressWarnings("restriction")
@AImplementation(value = TemplateService.class, runtime = ERuntime.DEFAULT)
public class TemplateServiceImpl extends TemplateService {

  /** The Constant log. */
  private static final Log LOGGER = LogFactory.getLog(TemplateServiceImpl.class);

  /** The Constant MIME_HTML_PATTERN. */
  private static final Pattern MIME_HTML_PATTERN = Pattern.compile(".*html.*");

  /** The helper. */
  private final ServiceHelper<ITemplateVersionListItem,ITemplateVersion,Long,ITemplateVersionCriteria> helper;

  /** The helper. */
  private final ServiceHelper<ITemplateListItem,ITemplate,Long,ITemplateCriteria> templateHelper;

  /** The helper. */
  private final ServiceHelper<ITemplateCategoryListItem,ITemplateCategory,Long,ICategoryCriteria> categoryHelper;

  /** The mime table. */
  private final FileNameMap mimeTable;

  /**
   * The Constructor.
   */
  protected TemplateServiceImpl() {
    super();

    helper =
        new ServiceHelper<ITemplateVersionListItem,ITemplateVersion,Long,ITemplateVersionCriteria>(
            TemplateVersionDataService.getInstance());
    templateHelper =
        new ServiceHelper<ITemplateListItem,ITemplate,Long,ITemplateCriteria>(
            TemplateDataService.getInstance());
    categoryHelper =
        new ServiceHelper<ITemplateCategoryListItem,ITemplateCategory,Long,ICategoryCriteria>(
            TemplateCategoryDataService.getInstance());
    mimeTable = MimeTable.loadTable();
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#add(org.infodavid.common.services.
   * IApplicationContext, org.infodavid.common.model.IDataObject)
   */
  @Override
  public long add(final IApplicationContext context, final ITemplateVersion value)
      throws PersistenceException, ServiceException {
    helper.validate(context, getValidator(), value);

    final IInterceptorEvent evt = preExecute(context, ADD_ACTION, value);

    if (value.getTemplate().getKey() == null) {
      templateHelper.add(context, value.getTemplate());
    }

    final long key = helper.add(context, value).longValue();

    postExecute(evt.setResults(value));

    LOGGER.info("A new Template has been added (" + key + ')');

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("A new Template has been added: " + value + " (" + key + ')');
    }

    return key;
  }

  /**
   * Extract subject.
   * @param result the result
   */
  private void extractSubject(ITemplateVersion result) {
    if (result != null) {
      if (MIME_HTML_PATTERN.matcher(result.getMimeType()).matches()) {
        final HTMLEditorKit editor = new HTMLEditorKit();
        final Document doc = editor.createDefaultDocument();
        InputStream in = new ByteArrayInputStream(result.getData());

        try {
          editor.read(in, doc, 0);

          if (doc instanceof HTMLDocument) {
            result.setSubject((String)((HTMLDocument)doc).getProperty(HTMLDocument.TitleProperty));
          }
        }
        catch (Exception e) {
          LOGGER.warn("An error occurs while reading title of HTML template: " + result.getName());
        }
      }
    }
  }

  /**
   * Load default template.
   * @param context the context
   * @param name the name
   * @param locale the locale
   * @return the i template version
   * @throws Exception the exception
   */
  protected ITemplateVersion loadDefaultTemplate(final IApplicationContext context,
      final String name, final Locale locale) throws Exception {
    final boolean isDebugEnabled = LOGGER.isDebugEnabled();
    final StringBuffer filename = new StringBuffer();

    if (isDebugEnabled) {
      LOGGER.debug("Using locale: " + locale.getDisplayLanguage());
    }

    filename.append(TEMPLATES_FOLDER);
    filename.append('/');
    filename.append(locale.getLanguage());
    filename.append('/');
    filename.append(name);

    final IUser sys =
        UserDataService.getInstance().findByName(context.getPersistenceSession(), IUser.SYSTEM);
    final ITemplateVersion result = create();

    if (isDebugEnabled) {
      LOGGER.debug("Loading default template using path: " + filename.toString());
    }

    final byte[] data =
        IOUtil.getInstance().readFile(getClass().getResourceAsStream(filename.toString()));

    result.setComment("Default template loaded");// TODO resource bundle
    result.setData(data);
    result.setEncoding(ITemplateVersion.DEFAULT_ENCODING);
    result.setName(name);
    result.setRevision(1);
    result.setMimeType(mimeTable.getContentTypeFor(name));
    result.setLanguage(locale);
    result.setUser(sys);
    result.getOwnerDetails().setUser(sys);
    result.getOwnerDetails().setVisibility(EVisibility.PUBLIC);

    extractSubject(result);

    if (isDebugEnabled) {
      LOGGER.debug("Default template: " + result);
    }

    return result;
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.services.TemplateService#findLastVersionByName(org.infodavid.common.services
   * .IApplicationContext, java.lang.String, java.util.Locale)
   */
  @Override
  public ITemplateVersion findLastVersionByName(final IApplicationContext context,
      final String value, final Locale locale) throws PersistenceException, ServiceException {
    if (StringUtils.isEmpty(value)) {
      throw new IllegalArgumentException(ITemplate.NAME);
    }

    ServiceUtil.validate(context);

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, value);
    final Locale loc;

    if (locale == null) {
      loc = ApplicationSettingsService.getInstance().getDefaultLocale(context);
    }
    else {
      loc = locale;
    }

    ITemplateVersion result =
        ((TemplateVersionDataService)helper.getDataService()).findLastVersionByName(
            context.getPersistenceSession(), value, loc);

    if (result == null) {
      try {
        result = loadDefaultTemplate(context, value, loc);

        if (result.getTemplate().getKey() == null) {
          templateHelper.add(context, result.getTemplate());
        }

        helper.add(context, result);
      }
      catch (final Exception e) {
        handle(e, "An error occurs while loading template: " + value, this);
      }
    }

    postExecute(evt.setResults(result));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.TemplateService#findByLastVersion(org.infodavid.common.services.
   * IApplicationContext, java.lang.Long)
   */
  @Override
  public ITemplateVersion findByLastVersion(final IApplicationContext context, final Long key)
      throws PersistenceException, ServiceException {
    ServiceUtil.validate(key);
    ServiceUtil.validate(context);

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, key);
    final ITemplateVersion result =
        ((TemplateVersionDataService)helper.getDataService()).findLastVersion(
            context.getPersistenceSession(), key);

    postExecute(evt.setResults(result));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#get(org.infodavid.common.services.
   * IApplicationContext, long)
   */
  @SuppressWarnings("boxing")
  @Override
  public ITemplateVersion get(final IApplicationContext context, final long key)
      throws PersistenceException, ServiceException {
    helper.validate(context, key);

    final IInterceptorEvent evt = preExecute(context, GET_ACTION, key);
    final ITemplateVersion result = helper.get(context, Long.valueOf(key));

    postExecute(evt.setResults(result));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.TemplateService#getTemplate(org.infodavid.common.services.
   * IApplicationContext, long)
   */
  @Override
  @SuppressWarnings("boxing")
  public ITemplate getTemplate(final IApplicationContext context, final long key)
      throws PersistenceException, ServiceException {
    helper.validate(context, key);

    final IInterceptorEvent evt = preExecute(context, GET_ACTION, key);
    final ITemplate result =
        TemplateDataService.getInstance().findByPrimaryKey(context.getPersistenceSession(), key);

    postExecute(evt.setResults(result));

    return result;
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.services.TemplateService#findTemplateByName(org.infodavid.common.services.
   * IApplicationContext, java.lang.String, java.util.Locale)
   */
  @Override
  public ITemplate findTemplateByName(final IApplicationContext context, final String value,
      final Locale locale) throws PersistenceException, ServiceException {
    if (StringUtils.isEmpty(value)) {
      throw new IllegalArgumentException(ITemplate.NAME);
    }

    ServiceUtil.validate(context);

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, value);
    final Locale loc;

    if (locale == null) {
      loc = ApplicationSettingsService.getInstance().getDefaultLocale(context);
    }
    else {
      loc = locale;
    }

    final ITemplate result =
        TemplateDataService.getInstance().findByName(context.getPersistenceSession(), value, loc);

    postExecute(evt.setResults(result));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#getEntities(org.infodavid.common.services.
   * IApplicationContext, java.util.List, org.infodavid.common.services.ISearchCriteria)
   */
  @Override
  public long findEntities(final IApplicationContext context,
      final List<ITemplateVersionListItem> entities, final ITemplateVersionCriteria criteria)
      throws PersistenceException, ServiceException {
    ServiceUtil.validate(context);
    helper.validate(entities);

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, criteria);
    final long result =
        ((TemplateVersionDataService)helper.getDataService()).find(context.getPersistenceSession(),
            entities, criteria);

    postExecute(evt.setResults(entities));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#remove(org.infodavid.common.services.
   * IApplicationContext, long)
   */
  @Override
  public void remove(final IApplicationContext context, final long key)
      throws PersistenceException, ServiceException {
    throw new IllegalOperationException("A Template version can not be removed (" + key + ')');
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#update(org.infodavid.common.services.
   * IApplicationContext, org.infodavid.common.model.IDataObject)
   */
  @Override
  public void update(final IApplicationContext context, final ITemplateVersion value)
      throws PersistenceException, ServiceException {
    add(context, value); // a version is always added and never updated using this service (see data
    // service)
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#add(org.infodavid.common.services.
   * IApplicationContext, org.infodavid.common.model.IDataObject)
   */
  @Override
  public long addCategory(final IApplicationContext context, final ITemplateCategory value)
      throws PersistenceException, ServiceException {
    categoryHelper.validate(context, getCategoryValidator(), value);

    final IInterceptorEvent evt = preExecute(context, ADD_ACTION, value);
    final long key = categoryHelper.add(context, value).longValue();

    postExecute(evt.setResults(value));

    LOGGER.info("A new category has been added (" + key + ')');

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("A new category has been added: " + value + " (" + key + ')');
    }

    return key;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#getEntities(org.infodavid.common.services.
   * IApplicationContext, java.util.List, org.infodavid.common.services.ISearchCriteria)
   */
  @Override
  public long findCategories(final IApplicationContext context,
      final List<ITemplateCategoryListItem> entities, final ICategoryCriteria criteria)
      throws PersistenceException, ServiceException {
    ServiceUtil.validate(context);
    categoryHelper.validate(entities);

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, criteria);
    final long result =
        ((TemplateCategoryDataService)categoryHelper.getDataService()).find(
            context.getPersistenceSession(), entities, criteria);

    postExecute(evt.setResults(entities));

    return result;
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.services.TemplateCategoryService#findByName(org.infodavid.common.services.
   * IApplicationContext, java.lang.String)
   */
  @Override
  public ITemplateCategory findCategoryByName(final IApplicationContext context, final String value)
      throws PersistenceException, ServiceException {
    if (StringUtils.isEmpty(value)) {
      throw new IllegalArgumentException(ITemplateCategory.NAME);
    }

    ServiceUtil.validate(context);

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, value);
    final ITemplateCategory result =
        ((TemplateCategoryDataService)categoryHelper.getDataService()).findByName(
            context.getPersistenceSession(), value);

    postExecute(evt.setResults(result));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#get(org.infodavid.common.services.
   * IApplicationContext, long)
   */
  @SuppressWarnings("boxing")
  @Override
  public ITemplateCategory getCategory(final IApplicationContext context, final long key)
      throws PersistenceException, ServiceException {
    categoryHelper.validate(context, key);

    final IInterceptorEvent evt = preExecute(context, GET_ACTION, key);
    final ITemplateCategory result = categoryHelper.get(context, Long.valueOf(key));

    postExecute(evt.setResults(result));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#remove(org.infodavid.common.services.
   * IApplicationContext, long)
   */
  @SuppressWarnings({
      "boxing", "rawtypes", "unchecked"
  })
  @Override
  public void removeCategory(final IApplicationContext context, final long key)
      throws PersistenceException, ServiceException {
    categoryHelper.validate(context, key);

    final IInterceptorEvent evt = preExecute(context, REMOVE_ACTION, key);
    final ITemplateCategory existing = categoryHelper.get(context, Long.valueOf(key));
    final ICategory parent = existing == null ? null : existing.getParent();

    if (parent != null) {
      parent.removeSubCategory(existing);
    }

    categoryHelper.remove(context, existing);
    postExecute(evt.setResults(existing));

    LOGGER.info("A category has been removed (" + key + ')');

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("A category has been removed: " + existing + " (" + key + ')');
    }
  }

  /*
   * (non-Javadoc)
   * @see org.infodavid.common.services.TemplateService#updateCategory(org.infodavid.common.services.
   * IApplicationContext, org.infodavid.common.model.ITemplateCategory)
   */
  @Override
  public void updateCategory(final IApplicationContext context, final ITemplateCategory value)
      throws PersistenceException, ServiceException {
    categoryHelper.validate(context, getCategoryValidator(), value);

    final IInterceptorEvent evt = preExecute(context, UPDATE_ACTION, value);

    categoryHelper.update(context, value);
    postExecute(evt.setResults(value));

    LOGGER.info("A category has been updated (" + value.getKey() + ')');

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("A category has been updated: " + value + " (" + value.getKey() + ')');
    }
  }
}
